////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2008 Michiel Helvensteijn                                    //
//                                                                            //
// This file is part of the Mist compiler.                                    //
//                                                                            //
// The Mist compiler is free software: you can redistribute it and/or modify  //
// it under the terms of the GNU General Public License as published by       //
// the Free Software Foundation, either version 3 of the License, or          //
// (at your option) any later version.                                        //
//                                                                            //
// The Mist compiler is distributed in the hope that it will be useful,       //
// but WITHOUT ANY WARRANTY; without even the implied warranty of             //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              //
// GNU General Public License for more details.                               //
//                                                                            //
// You should have received a copy of the GNU General Public License          //
// along with the Mist compiler.  If not, see <http://www.gnu.org/licenses/>. //
////////////////////////////////////////////////////////////////////////////////

#ifndef COMPILERFIXTURE_HPP
#define COMPILERFIXTURE_HPP

/******************************************************************************
 * Includes                                                                   *
 ******************************************************************************/

#include <string>
using std::string;

#include <set>
using std::set;

#include <map>
using std::map;

#include <fstream>
using std::ifstream;

#include <boost/shared_ptr.hpp>
using boost::shared_ptr;

#include <boost/process.hpp>
namespace bp = ::boost::process;

#include <boost/filesystem.hpp>
namespace bf = ::boost::filesystem;

#include <boost/algorithm/string.hpp>
using boost::iends_with;

using std::pair;

#include "symboltree.hpp"
#include "outputproducer.hpp"
#include "parser.hpp"
#include "messages.hpp"
#include "assert.hpp"
#include "visitors/declarationvisitor.hpp"
#include "visitors/typevisitor.hpp"
#include "visitors/accesstypevisitor.hpp"
#include "visitors/cppvisitor.hpp"

/******************************************************************************
 * Testcase struct                                                            *
 ******************************************************************************/

struct TestCase {
	string fileBase;
	set<string> runs;
};

bool operator<(const TestCase& a, const TestCase& b) {
	return a.fileBase < b.fileBase;
}

/******************************************************************************
 * CompilerFixture class                                                      *
 ******************************************************************************/

struct CompilerFixture {
	public:
		
		CompilerFixture() : _progressBar(NULL) {
		} // intentionally empty
		
		void setup(const string& filebase) {
			_fileBase = filebase;
			
			_messages.reset(new Messages);
			_symbolRoot.reset(new UnnamedSymbol);
			
			ifstream finOriginal((_fileBase + ".m").c_str());
			
			assert(finOriginal.is_open());
			try {
				_tree = parse<Declarations>(finOriginal, _messages, (_fileBase + ".m"));
			} catch (ParserException e) {
				_messages->add( Error, e.message() );
				return;
			}
			
			_symbolRoot->add_default_symbols();
			find_declarations(_tree, _messages, _symbolRoot);
			test_types       (_tree, _messages);
			set_access_types (_tree, _messages);
		}
		
		void CHECK(bool condition, const string& message) {
			if (!condition)
				cout << "\n\n";
			BOOST_CHECK_MESSAGE(condition, message);
			if (!condition) {
				unsigned long progress = _progressBar->count();
				_progressBar->restart(_progressBar->expected_count());
				(*_progressBar) += progress;
			}
		}
		
		void REQUIRE(bool condition, const string& message) {
			if (!condition)
				cout << "\n\n";
			BOOST_REQUIRE_MESSAGE(condition, message);
			if (!condition) {
				unsigned long progress = _progressBar->count();
				_progressBar->restart(_progressBar->expected_count());
				(*_progressBar) += progress;
			}
		}
		
		string compile() {
			bp::launcher l;
			l.set_stdout_behavior(bp::redirect_stream);
			l.set_stdin_behavior (bp::redirect_stream);
			l.set_stderr_behavior(bp::silent_stream);
			
			bp::command_line cl("g++");
			cl.argument("-x");
			cl.argument("c++");
			cl.argument("-O0");
			cl.argument("-lgmpxx");
			cl.argument("-lgmp");
			cl.argument("-o");
			cl.argument(_fileBase);
			cl.argument("-");
			
			bp::child gpp = l.start(cl);
			
			gpp.get_stdin() << cppProgram(_tree);
			gpp.get_stdin().flush();
			gpp.get_stdin().close();
			
			string messages;
			string line;
			while (std::getline(gpp.get_stdout(), line))
				messages += line + "\n";
			
			bp::status status = gpp.wait();
			
			assert(status.exited());
			
			return messages;
		}
		
		void remove() {
			bp::launcher l;
			l.set_stdout_behavior(bp::silent_stream);
			l.set_stdin_behavior (bp::silent_stream);
			l.set_stderr_behavior(bp::silent_stream);
			
			bp::command_line cl("rm");
			cl.argument(_fileBase);
			
			bp::child child = l.start(cl);
			
			bp::status status = child.wait();
			
			assert(status.exited());
		}
		
		shared_ptr<Messages> messages() const {
			assert(_messages);
			return _messages;
		}
		
		shared_ptr<Declarations> tree() const {
			assert(_tree);
			return _tree;
		}
		
		const string& file_base() const {
			return _fileBase;
		}
		
		template <class T>
		string diff(const string& extension, const OutputProducer<T>& output) {
			bp::launcher l;
			l.set_stdout_behavior(bp::redirect_stream);
			l.set_stdin_behavior (bp::redirect_stream);
			l.set_stderr_behavior(bp::silent_stream);
			
			bp::command_line cl("diff");
			cl.argument(_fileBase + "." + extension);
			cl.argument("-");
			
			bp::child diff = l.start(cl);
			
			diff.get_stdin() << output;
			diff.get_stdin().flush();
			diff.get_stdin().close();
			
			string result;
			string line;
			while (std::getline(diff.get_stdout(), line))
				result += line + "\n";
			
			bp::status status = diff.wait();
			
			assert(status.exited());
			
			return result;
		}
		
		pair<string, string> run_diff(string programName, string inFile, string outFile) const {
			bp::pipeline l;
			l.set_stdout_behavior(bp::redirect_stream);
			l.set_stderr_behavior(bp::redirect_stream);
			
			bp::command_line cl1("cat");
			cl1.argument(inFile);
			
			bp::command_line cl2(programName);
			
			bp::command_line cl3("diff");
			cl3.argument(outFile);
			cl3.argument("-");
			
			l.add(cl1).add(cl2).add(cl3);
			
			bp::children program = l.start();
			
			string line;
			pair<string, string> result;
			while (true) {
				if (   program.get_stdout().peek() == EOF
				    && program.get_stderr().peek() == EOF)
					break;
				if (program.get_stdout().peek() != EOF) {
					std::getline(program.get_stdout(), line);
					result.first += line + "\n";
				}
				if (program.get_stderr().peek() != EOF) {
					std::getline(program.get_stderr(), line);
					result.second += line + "\n";
				}
			}
			
			bp::status status = program.wait();
			
			assert(status.exited());
			
			return result;
		}
		
		set<TestCase> find_test_cases(string dir) {
			map<string, TestCase> resultMap;
			
			bf::path path(dir);
			
			bf::directory_iterator end_itr;
			
			for (bf::directory_iterator itr(path); itr != end_itr; ++itr) {
				string name = itr->leaf();
				
				if (iends_with(name, ".m")) {
					string base = name.substr(0, name.length() - 2);
					resultMap[base].fileBase = dir + "/" + base;
				}
			}
			
			for (bf::directory_iterator itr(path); itr != end_itr; ++itr) {
				string name = itr->leaf();
				
				if (iends_with(name, ".in")) {
					unsigned pos = name.rfind('.', name.length() - 4);
					string base = name.substr(0, pos);
					if (resultMap.find(base) != resultMap.end()) {
						resultMap[base].runs.insert(name.substr(pos + 1, name.length() - pos - 4));
					}
				} else if (iends_with(name, ".out")) {
					unsigned pos = name.rfind('.', name.length() - 5);
					string base = name.substr(0, pos);
					if (resultMap.find(base) != resultMap.end()) {
						resultMap[base].runs.insert(name.substr(pos + 1, name.length() - pos - 5));
					}
				}
			}
			
			set<TestCase> result;
			
			for (map<string, TestCase>::iterator i = resultMap.begin(); i != resultMap.end(); ++i)
				result.insert(i->second);
			
			return result;
		}
		
		void start_progress_bar(unsigned maxValue) {
			delete _progressBar;
			_progressBar = new progress_display(maxValue);
		}
		
		void progress(unsigned value = 1) {
			assert(_progressBar);
			(*_progressBar) += value;
		}
	
	private:
		
		shared_ptr<Messages>      _messages;
		shared_ptr<UnnamedSymbol> _symbolRoot;
		shared_ptr<Declarations>  _tree;
		string _fileBase;
		progress_display* _progressBar;
};

#endif // COMPILERFIXTURE_HPP
